import requests
import json
import os
import datetime
import time
import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad


def read_config(config_path):
    """读取 JSON 配置文件."""
    if not os.path.exists(config_path):
        raise FileNotFoundError("配置文件不存在")
    with open(config_path, 'r') as config_file:
        config = json.load(config_file)
    return config


def write_config(config, config_path):
    """将配置写入 JSON 文件."""
    with open(config_path, 'w') as config_file:
        json.dump(config, config_file, indent=4)


def login(username, password, api_url):
    """使用API登录并获取token."""
    headers = {
        'Accept': '*/*',
        'Accept-Encoding': 'gzip, deflate, br',
        'Connection': 'keep-alive',
        'Content-Type': 'application/json',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
    }
    
    data = {
        "username": username,
        "password": password
    }
    
    try:
        response = requests.post(api_url, headers=headers, json=data)
        response_data = response.json()
        
        if response_data.get("success"):
            print("登录成功")
            return response_data.get("token"), response_data.get("user_info")
        else:
            print(f"登录失败: {response_data.get('message', '未知错误')}")
            return None, None
    except Exception as e:
        print(f"登录过程中发生错误：{e}")
        return None, None


def check_token_validity(token):
    """检查token是否有效."""
    url = 'https://libyuyue.qlu.edu.cn/v4/member/my'
    headers = {
        'Accept': 'application/json, text/plain, */*',
        'Accept-Encoding': 'gzip, deflate, br, zstd',
        'Accept-Language': 'zh-CN,zh;q=0.9',
        'Connection': 'keep-alive',
        'Content-Type': 'application/json',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36',
        'X-Requested-With': 'XMLHttpRequest',
        'authorization': f'bearer{token}'
    }
    
    try:
        response = requests.post(url, headers=headers, json={})
        # 使用utf-8-sig编码处理可能存在的BOM
        response_text = response.content.decode('utf-8-sig')
        response_data = json.loads(response_text)
        
        if response_data.get("code") == 0:
            user_info = response_data.get("data", {})
            print(f"Token有效，用户: {user_info.get('name')}")
            return True
        else:
            print("Token已失效")
            return False
    except Exception as e:
        print(f"验证Token时发生错误：{e}")
        return False


def get_segment(token, area_id, datatime):
    """获取该区域的segment."""
    url = f"https://libyuyue.qlu.edu.cn/v4/Space/map"
    
    headers = {
        'Accept': 'application/json, text/plain, */*',
        'Accept-Encoding': 'gzip, deflate, br, zstd',
        'Accept-Language': 'zh-CN,zh;q=0.9',
        'Connection': 'keep-alive',
        'Content-Type': 'application/json',
        'Origin': 'https://libyuyue.qlu.edu.cn',
        'Referer': 'https://libyuyue.qlu.edu.cn/h5/index.html',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36',
        'X-Requested-With': 'XMLHttpRequest',
        'authorization': f'bearer{token}',
        'sec-ch-ua': '"Chromium";v="134", "Not:A-Brand";v="24"',
        'sec-ch-ua-mobile': '?0',
        'sec-ch-ua-platform': '"Windows"'
    }
    
    try:
        print("获取时间段中...")
        response = requests.post(url, headers=headers, json={"id": area_id})
        # 使用utf-8-sig编码处理可能存在的BOM
        response_text = response.content.decode('utf-8-sig')
        response_data = json.loads(response_text)
        
        if response_data.get("code") == 0:
            segments = response_data.get("data", {}).get("date", {}).get("list", [])
            
            if datatime == '0' and segments:
                # time为0时，获取第一天的时间段
                segment_id = segments[0].get("times", [])[0].get("id")
                day = segments[0].get("day")
                return segment_id, day
            elif datatime == '1' and len(segments) > 1:
                # time为1时，获取第二天的时间段
                segment_id = segments[1].get("times", [])[0].get("id")
                day = segments[1].get("day")
                return segment_id, day
            else:
                # 默认获取最后一个有效时间段
                if segments:
                    segment_id = segments[-1].get("times", [])[0].get("id")
                    day = segments[-1].get("day")
                    return segment_id, day
                
            print("未找到有效的时间段")
            return None, None
        else:
            print(f"获取时间段失败: {response_data.get('message', '未知错误')}")
            return None, None
    except Exception as e:
        print(f"获取时间段时发生错误：{e}")
        return None, None


def wait_until_target_time(target_time_str):
    """等待目标时间, 如果当前时间大于目标时间，则等待到第二天该时间点."""
    now = datetime.datetime.now()
    target_time = datetime.datetime.strptime(target_time_str, "%H:%M").time()
    
    if now.time() >= target_time:
        target_datetime = datetime.datetime.combine(now.date() + datetime.timedelta(days=1), target_time)
    else:
        target_datetime = datetime.datetime.combine(now.date(), target_time)
    
    wait_seconds = (target_datetime - now).total_seconds()
    print(f"等待{wait_seconds}秒到{target_time_str}...")
    time.sleep(wait_seconds)
    print(f"等待完成，当前时间为{datetime.datetime.now().strftime('%H:%M')}")


def get_key_and_iv(date=None):
    """
    生成与JavaScript相同的密钥和IV
    :param date: 日期对象，默认为当前日期
    :return: 密钥和IV的字节串
    """
    if date is None:
        date = datetime.datetime.now()
    
    # 获取日期并格式化为YYYYMMDD
    current_date = date.strftime("%Y%m%d")
    # 反转日期字符串并拼接
    reversed_date = current_date[::-1]
    # 组合成完整的密钥
    key = current_date + reversed_date
    # IV是固定的
    iv = "ZZWBKJ_ZHIHUAWEI"
    
    return key.encode('utf-8'), iv.encode('utf-8')


def encrypt(data, date=None):
    """
    加密数据
    :param data: 要加密的数据字典
    :param date: 用于生成密钥的日期对象，默认为当前日期
    :return: Base64编码的加密文本
    """
    try:
        # 获取密钥和IV
        key, iv = get_key_and_iv(date)
        
        # 将数据转换为JSON字符串并编码
        data_json = json.dumps(data).encode('utf-8')
        
        # 使用PKCS7填充
        padded_data = pad(data_json, AES.block_size)
        
        # 创建AES加密器
        cipher = AES.new(key, AES.MODE_CBC, iv)
        
        # 加密
        encrypted_data = cipher.encrypt(padded_data)
        
        # Base64编码
        result = base64.b64encode(encrypted_data).decode('utf-8')
        return result
    
    except Exception as e:
        print(f"加密失败: {str(e)}")
        return None


def encrypt_with_current_date(data):
    """
    使用当前日期加密数据
    :param data: 要加密的数据字典
    :return: Base64编码的加密文本
    """
    current_date = datetime.datetime.now()
    return encrypt(data, current_date)


def reserve_seat(token, seat_id, segment_id, day):
    """预约座位."""
    url = "https://libyuyue.qlu.edu.cn/v4/space/confirm"
    
    # 准备固定格式的加密数据
    data = {
        "seat_id": seat_id,
        "segment": segment_id,
        "day": day,
        "start_time": "",
        "end_time": ""
    }
    
    # 使用当前日期加密数据，而不是segment对应的day
    aesjson = encrypt_with_current_date(data)
    if not aesjson:
        return "加密数据失败，无法预约"
    
    headers = {
        'Accept': 'application/json, text/plain, */*',
        'Accept-Encoding': 'gzip, deflate, br, zstd',
        'Accept-Language': 'zh-CN,zh;q=0.9',
        'Connection': 'keep-alive',
        'Content-Type': 'application/json',
        'Origin': 'https://libyuyue.qlu.edu.cn',
        'Referer': 'https://libyuyue.qlu.edu.cn/h5/index.html',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36',
        'X-Requested-With': 'XMLHttpRequest',
        'authorization': f'bearer{token}',
        'sec-ch-ua': '"Chromium";v="134", "Not:A-Brand";v="24"',
        'sec-ch-ua-mobile': '?0',
        'sec-ch-ua-platform': '"Windows"'
    }
    
    print(f"正在预约座位: {seat_id}, 时间段: {segment_id}...")
    try:
        response = requests.post(url, headers=headers, json={"aesjson": aesjson})
        
        # 使用utf-8-sig编码处理可能存在的BOM
        response_text = response.content.decode('utf-8-sig')
        response_data = json.loads(response_text)
        
        if response_data.get("code") == 0:
            return f"预约成功: {response_data.get('message', '操作成功')}"
        else:
            return f"预约失败: {response_data.get('message', '未知错误')}"
    except Exception as e:
        return f"预约过程中发生错误：{e}"


def send_wxpusher_notification(content, spt):
    """发送 wxpusher 通知."""
    url = "https://wxpusher.zjiecode.com/api/send/message/simple-push"
    payload = {
        "content": content,
        "summary": "座位预约结果",
        "contentType": 1,
        "spt": spt
    }
    headers = {
        "Content-Type": "application/json"
    }
    try:
        response = requests.post(url, json=payload, headers=headers)
        if response.status_code == 200:
            print("通知发送成功")
        else:
            print(f"通知发送失败: {response.text}")
    except Exception as e:
        print(f"发送通知时发生错误: {e}")


if __name__ == "__main__":
    print("=========================欢迎使用图书馆预约脚本==========================")
    print("===========================本项目为开源项目=============================")
    print("======开源地址:https://gitee.com/xxxhhy/qlulib-reservation ===========")
    print("=========================如果是购入的请立即退款==========================")
    print("")
    
    # 读取配置文件
    config_path = 'config.json'
    try:
        config = read_config(config_path)
        print("配置加载成功")
    except FileNotFoundError:
        print("配置文件不存在，请检查路径和文件名。")
        exit(0)
    
    # 获取配置信息
    username = config.get('Credentials', {}).get('username')
    password = config.get('Credentials', {}).get('password')
    api_url = config.get('api', {}).get('api')
    area_id = config.get('seat', {}).get('area')
    seat_id = config.get('seat', {}).get('seatid')
    datatime = config.get('time', {}).get('datatime')
    target_time_str = config.get('time', {}).get('time')
    spt = config.get('api', {}).get('spt')
    
    # 验证必要配置
    if not username or not password or not api_url:
        print("请在配置文件中正确填写用户名、密码和API地址")
        exit(0)
    
    if not area_id or not seat_id:
        print("请在配置文件中输入座位号和座位区域")
        exit(0)
    
    if datatime != "0" and datatime != "1":
        print("请在配置文件中设置datatime为0或者1")
        exit(0)
    
    # 获取保存的token
    token = config.get('token', None)
    
    # 检查token有效性或重新登录
    if token and check_token_validity(token):
        print("使用已保存的token")
    else:
        print("需要重新登录获取token")
        token, user_info = login(username, password, api_url)
        if token:
            config['token'] = token
            if user_info:
                config['user_info'] = user_info
            write_config(config, config_path)
            print("token已保存到配置文件")
        else:
            print("登录失败，无法获取token")
            exit(0)
    
    # 等待目标时间
    if target_time_str:
        print(f"当前时间为{datetime.datetime.now().strftime('%H:%M')}")
        print(f"目标时间为{target_time_str}")
        wait_until_target_time(target_time_str)
    
    # 获取segment
    segment_id, day = get_segment(token, area_id, datatime)
    if not segment_id or not day:
        print("获取时间段失败，无法预约")
        exit(0)
    
    print(f"成功获取时间段 ID: {segment_id}, 日期: {day}")
    
    # 预约座位
    max_retries = 3
    result = None
    
    for attempt in range(max_retries):
        result = reserve_seat(token, seat_id, segment_id, day)
        print(result)
        
        if "预约成功" in result:
            break
        
        if attempt < max_retries - 1:
            print(f"预约失败，正在重试... ({attempt + 1}/{max_retries})")
            time.sleep(2)  # 短暂等待后重试
    
    # 发送微信通知
    if spt and result:
        send_wxpusher_notification(result, spt)
    elif not spt:
        print("未配置 SPT，跳过发送通知")
